package mk.jug.academy.service;

import mk.jug.academy.exception.BusinessException;
import mk.jug.academy.model.Lecturer;
import mk.jug.academy.model.Page;
import mk.jug.academy.model.Student;
import mk.jug.academy.model.StudentReview;
import mk.jug.academy.model.StudentReviewStatus;
import mk.jug.academy.preconditions.ErrorCodes;
import mk.jug.academy.testing.bdd.IntegrationBehavior;
import mk.jug.academy.testing.factory.LecturerTestFactory;
import mk.jug.academy.testing.factory.StudentReviewTestFactory;
import mk.jug.academy.testing.factory.StudentTestFactory;
import mk.jug.academy.testing.util.TestHelper;

import org.springframework.beans.factory.annotation.Autowired;

import com.googlecode.instinct.marker.annotate.Specification;
import com.googlecode.instinct.marker.annotate.Specification.SpecificationState;
import com.googlecode.instinct.marker.annotate.Subject;

/**
 * Examines and verifies the behavior regarding the addition
 * and management of {@link StudentReview}s. 
 *
 * @author Ice.Penov
 */
public class StudentReviewFunctionality extends IntegrationBehavior {
	
	@Autowired private StudentReviewService studentReviewService;
	@Autowired private StudentService studentService;
	@Autowired private LecturerService lecturerService;
	
	@Subject private Student student;
	@Subject private Lecturer lecturer;
	@Subject private StudentReview studentReview;
	@Subject private StudentReview studentReview1;
	@Subject private StudentReview studentReview2;
	@Subject private StudentReview studentReview3;
	
	@Override
	protected void beforeHook() {
		student = StudentTestFactory.createStudent();
		lecturer = LecturerTestFactory.createLecturer();
		studentReview = StudentReviewTestFactory.createStudentReview();
		studentReview1 = StudentReviewTestFactory.createStudentReview();
		studentReview2 = StudentReviewTestFactory.createStudentReview();
		studentReview3 = StudentReviewTestFactory.createStudentReview();
		
		TestHelper.init(studentService);
		TestHelper.init(lecturerService);
		TestHelper.init(studentReviewService);
	}
	
	/**
	 * User action:
	 * A {@link Lecturer} starts reviewing a {@link Student}.
	 * 
	 * Expected behavior:
	 * 1. The {@link StudentReview} is listed as {@link StudentReviewStatus#BEING_DEFINED}
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void startReviewing() {
		TestHelper.prepareActiveLecturer(lecturer);
		TestHelper.prepareActiveStudent(student);
		//start the review
		studentReview = StudentReviewTestFactory.createStudentReview();
		studentReviewService.startReview(student, lecturer, studentReview);
		//expectations
		expect.that(studentReviewService.listCurrentlyDefined(1, 2).getItems()).containsItem(studentReview);
		expect.that(studentReviewService.listCurrentlyDefined(1, 2).getItems()).isOfSize(1);
	}
	
	/**
	 * User action:
	 * A {@link Lecturer} continues reviewing a {@link Student} using an existing {@link StudentReview}.
	 * 
	 * Expected behavior:
	 * 1. The modified {@link StudentReview} is still listed as {@link StudentReviewStatus#BEING_DEFINED}
	 * 2. {@link IllegalStateException} should be thrown if attempt is made to continue reviewing other than {@link StudentReviewStatus#BEING_DEFINED} review 
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void continueReviewing() {
		TestHelper.prepareStartedReview(student, lecturer, studentReview);
		studentReviewService.continueReview(studentReview);
		//expectations
		expect.that(studentReviewService.listCurrentlyDefined(1, 2).getItems()).containsItem(studentReview);
		studentReview.setStatus(StudentReviewStatus.COMPLETE);
		new ExceptionExpectation() {
			public void execute() {
				studentReviewService.continueReview(studentReview);				
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_REVIEW_BEING_DEFINED_STATE);
		studentReview.setStatus(StudentReviewStatus.DELETED);
		new ExceptionExpectation() {
			public void execute() {
				studentReviewService.continueReview(studentReview);				
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_REVIEW_BEING_DEFINED_STATE);
	}
	
	/**
	 * User action:
	 * A {@link Lecturer} completes a review for a {@link Student}.
	 * 
	 * Expected behavior:
	 * 1. The {@link StudentReview} is listed as {@link StudentReviewStatus#COMPLETE}
	 * 2. {@link IllegalStateException} should be thrown if attempt is made to complete other than {@link StudentReviewStatus#BEING_DEFINED} review.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void completeReviewing() {
		TestHelper.prepareStartedReview(student, lecturer, studentReview);
		studentReviewService.completeReview(studentReview);
		//expectations
		expect.that(studentReviewService.listCurrentlyDefined(1, 2).getItems()).isEmpty();
		expect.that(studentReviewService.listCompleted(1, 2).getItems()).containsItem(studentReview);
		studentReview.setStatus(StudentReviewStatus.COMPLETE);
		new ExceptionExpectation() {
			public void execute() {
				studentReviewService.completeReview(studentReview);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_REVIEW_BEING_DEFINED_STATE);
		studentReview.setStatus(StudentReviewStatus.DELETED);
		new ExceptionExpectation() {
			public void execute() {
				studentReviewService.completeReview(studentReview);
			}
		}.checkForErrorCode(BusinessException.class, ErrorCodes.ERROR_STUDENT_REVIEW_BEING_DEFINED_STATE);
	}
	
	/**
	 * User action:
	 * Delete a {@link StudentReview}.
	 * 
	 *  Expected behavior:
	 *  1. The {@link StudentReview} is listed as {@link StudentReviewStatus#DELETED}
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void deleteReview() {
		TestHelper.prepareStartedReview(student, lecturer, studentReview);
		studentReviewService.deleteReview(studentReview);
		//expectations
		expect.that(studentReviewService.listDeleted(1, 2).getItems()).containsItem(studentReview);
		expect.that(studentReviewService.listDeleted(1, 2).getItems()).isOfSize(1);
	}
	
	/**
	 * User action:
	 * Fetch the {@link StudentReview}s in a paged manner.
	 * 
	 * Expected behavior:
	 * List the records as expected.
	 */
	@Specification(state = SpecificationState.COMPLETE)
	public void navigateStudentReviewsUsingPaging() {
		TestHelper.prepareActiveLecturer(lecturer);
		TestHelper.prepareActiveStudent(student);
		studentReviewService.startReview(student, lecturer, studentReview1);
		studentReviewService.startReview(student, lecturer, studentReview2);
		studentReviewService.startReview(student, lecturer, studentReview3);
		//open the pages.
		Page<StudentReview> page1 = studentReviewService.listCurrentlyDefined(1, 2);
		Page<StudentReview> page2 = studentReviewService.listCurrentlyDefined(2, 2);
		Page<StudentReview> page3 = studentReviewService.listCurrentlyDefined(3, 2);
		//expectations.
		//size:
		expect.that(page1.getTotalCount()).isEqualTo(3);
		expect.that(page2.getTotalCount()).isEqualTo(3);
		expect.that(page3.getTotalCount()).isEqualTo(3);
		expect.that(page1.getItems()).isOfSize(2);
		expect.that(page2.getItems()).isOfSize(1);
		expect.that(page3.getItems()).isEmpty();
		//check contents of the pages:
		expect.that(page1.getItems()).containsItems(studentReview3, studentReview2);
		expect.that(page2.getItems()).containsItem(studentReview1);
	}
	
}